﻿//
// Copyright (c) 2011 Canyala Innovation AB
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.WindowsAzure.StorageClient;
using System.Data.Services.Client;
using Canyala.Lagoon.Models;
using Canyala.Lagoon.Functional;
using System.Threading;

namespace Canyala.Lagoon.Storage
{
    /// <summary>
    /// Provides a repository for table service entities.
    /// </summary>
    /// <typeparam name="T">The type of entities in the repository</typeparam>
    public class TableEntityRepository<T> where T : TableServiceEntity
    {
        public readonly string _tableName;
        private readonly TableServiceContext _context;
        private readonly CloudTableClient _client;
        private readonly bool _wasCreated;

        private TableEntityRepository(CloudTableClient client, string tableName=null)
        {
            _client = client;
            _tableName = tableName ?? typeof(T).Name;
            _wasCreated = client.CreateTableIfNotExist(_tableName);
            _context = client.GetDataServiceContext();
        }

        public void Add(T entity)
            { _context.AddObject(_tableName, entity); }

        public void Update(T entity)
            { _context.UpdateObject(entity); }

        public void Delete(T entity)
            { _context.DeleteObject(entity); }

        private IEnumerable<T> Where(Expression<Func<T, bool>> predicate)
        {
            try
            {
                var query = _context.CreateQuery<T>(_tableName).AsTableServiceQuery();
                return query.Where(predicate).ToList();
            }
            catch (DataServiceQueryException exception)
            {
                var dataServiceClientException = exception.InnerException as DataServiceClientException;

                if (dataServiceClientException == null)
                    throw;
                
                if (dataServiceClientException.StatusCode == 404)
                    return Seq.Empty<T>();

                throw;
            }
        }
        
        private T FirstOrDefault(Expression<Func<T, bool>> predicate)
        {
            return Where(predicate).FirstOrDefault();
        }

        public IEnumerable<T> All
        {
            get
            {
                try
                {
                    var query = _context.CreateQuery<T>(_tableName).AsTableServiceQuery();
                    return query.AsEnumerable();
                }
                catch (DataServiceQueryException exception)
                {
                    var dataServiceClientException = exception.InnerException as DataServiceClientException;

                    if (dataServiceClientException == null)
                        throw;

                    if (dataServiceClientException.StatusCode == 404)
                        return Seq.Empty<T>();

                    throw;
                }
            }
        }

        public IEnumerable<T> AllInPartion(string partitionKey)
        {
            try
            {
                var query = _context.CreateQuery<T>(_tableName).AsTableServiceQuery();
                return query.Where(entity => entity.PartitionKey == partitionKey).AsEnumerable();
            }
            catch (DataServiceQueryException exception)
            {
                var dataServiceClientException = exception.InnerException as DataServiceClientException;

                if (dataServiceClientException == null)
                    throw;

                if (dataServiceClientException.StatusCode == 404)
                    return Seq.Empty<T>();

                throw;
            }
        }

        public T EntityOrDefault(TableEntityKey tableEntityKey)
        {
            return FirstOrDefault(entity => entity.PartitionKey == tableEntityKey.PartitionKey && entity.RowKey == tableEntityKey.RowKey);
        }

        public static TableEntityRepository<T> FromCloudTableClient(CloudTableClient client)
            { return new TableEntityRepository<T>(client); }

        public bool SaveChanges()
        {
            try
            {
                _context.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

                return true;
            }
            catch (DataServiceRequestException exception)
            {
                var clientException = exception.InnerException as DataServiceClientException;

                if (clientException != null)
                {
                    switch (clientException.StatusCode)
                    {
                        case 500:
                            Thread.Sleep(1500);
                            return false;

                        case 409:
                        case 412:
                            return false;
                    }
                }

                throw;
            }
        }

        public bool WasCreated 
            { get { return _wasCreated; } }

        public string TableName 
            { get { return _tableName; } }
    }
}
